/*-*-C-*-
 * Message import/export for the ResEd shell
 */

#include "resed.h"
#include "main.h"

#include "swicall.h"
#include "wimp.h"
#include "resformat.h"
#include "dbox.h"
#include "interactor.h"
#include "registry.h"
#include "saveas.h"

#include "class.h"
#include "document.h"
#include "genmsgs.h"
#include "icondefs.h"
#include "protocol.h"


static WindowPtr warnwin = NULL;

typedef struct
{
    DocumentPtr doc;
    char *buf;
    int numloaded;
    Bool goahead;
} ImportClosureRec, *ImportClosurePtr;

static ImportClosureRec closure;



/*
 * Load templates, etc.
 */

error * genmsgs_load_prototypes ()
{
    ER ( wimp_load_template("ImpWarn", &warnwin) );
    ER ( swi (Wimp_CreateWindow,  R1, &warnwin->visarea,
                OUT,  R0, &warnwin->handle,  END) );

    /* insert message */
    dbox_setstring (warnwin, I_IMPWARN_MESSAGE,
                             message_lookup (&msgs, "ImpWarn"));

    /* must be registered for interactive help */
    return registry_register_window (warnwin->handle,
                                       ImpWarnDbox, (void *) warnwin);
}


/******** EXPORT ********/


/*
 * Output messages to block of memory.
 * If buf is NULL, then count size required and return this.
 * If buf is non-NULL, then it points to a block of memory big
 * enough for the messages.  Fill it in.
 *
 * If 'selection' is set we only want the messages from the selected objects.
 *
 * NOTE: Messages that have a relocation record but whose wordtorelocate
 * contains -1 are not output.  However, the message counter is incremented
 * so that we can be sure to match the messages up properly when they are
 * re-imported later - even if the object into which the messages are
 * being imported has been changed such that this message DOES now exist (in
 * which case the import code will retain the message already in the object).
 */


static int write_messages (DocumentPtr doc, Bool selection, char *buf)
{
    int size = 0;
    ResourcePtr res;
    char comment [256];

    /* generate header line */
    sprintf (comment, message_lookup (&msgs, "MsgHdr"), RESF_VERSION);
    size += strlen (comment) + 1;
    if (buf)
    {
        sprintf (buf, "%s\n", comment);
        buf += strlen (buf);
    }

    /* construct generic section header */
    sprintf (comment, "%.*s", sizeof (comment) - 1,
                              message_lookup (&msgs, "MsgComment"));

    /* extract messages from each resource in turn */
    for (res = doc->resources; res; res = res->next)
    {
        int msgoffset = res->object->messagetableoffset;
        int reloffset = res->object->relocationtableoffset;
        char *body = (char *) &res->object->hdr.class +
                               res->object->hdr.bodyoffset;
        char *msgs;
        RelocationTablePtr relocs;
        int i, msgnum = -1;
        Bool docomment = TRUE;

        if (selection && res->selected == FALSE)
            continue;

        if (msgoffset <= 0 || reloffset <= 0)
            continue;           /* This object has none */

        /* Walk down the relocation table looking for Messages */
        msgs = (char *)res->object + msgoffset;
        relocs = (RelocationTablePtr) ((char *)res->object + reloffset);

        for (i = 0; i < relocs->numrelocations; i++)
        {
            if (relocs->relocations[i].directive == RELOCATE_MSGREFERENCE)
            {
                int *word =
                    (int *) (body + relocs->relocations[i].wordtorelocate);
                msgnum++;
                if (*word >= 0)
                {
                    char *msgtext = msgs + *word;
                    char tag[256];

                    if (docomment)
                    {
                        sprintf (tag, comment, res->object->hdr.name,
                                               res->class->classname);
                        size += strlen(tag) + 3;
                        if (buf)
                        {
                            sprintf (buf, "\n%s\n\n", tag);
                            buf += strlen(buf);
                        }
                        docomment = FALSE;
                    }

                    sprintf (tag, "%s|%d:", res->object->hdr.name, msgnum);
                    size += strlen(tag);
                    size += strlen(msgtext) + 1;

                    if (buf)
                    {
                        sprintf (buf, "%s%s\n", tag, msgtext);
                        buf += strlen(buf);
                    }
                }
            }
        }
    }
    return size;
}


/*
 * Called from the menu callback for Export Messages
 */

error * genmsgs_saveas_cb (
    SaveAsReason reason,
    Bool selection,
    char **buf,
    int *size,
    void *closure)
{
    DocumentPtr doc = (DocumentPtr) closure;
/*    dprintf("In genmsgs_saveas_cb %d\n" _ reason); */
    switch (reason)
    {
    case SaveAsGetSize:
        /* Recover latest versions of the doc's objects from CSEs */
        ER ( document_recover_document (doc) );
        *size = write_messages (doc, selection, NULL);
        break;
    case SaveAsGetBlock:
        *buf = malloc (*size + 1); /* must allow extra byte for final null */
        if (*buf == NULL)
            return error_lookup("NoMem");
        (void) write_messages (doc, selection, *buf);
        break;
    case SaveAsFreeBlock:
        free (*buf);
        break;
    }
    return NULL;
}



/******** IMPORT ********/


static error * import_messages (ImportClosurePtr clos)
{
    DocumentPtr doc = clos->doc;
    char *buf = clos->buf, *bufp;
    int numloaded = clos->numloaded;
    error *err = NULL;
    ResourcePtr res;
    int numbad = 0, nummal = 0;
    char **msgptr = NULL;
    int size;

    ER ( document_recover_document (doc) );
    
    /*
     * Loop over each resource and process
     */

    for (res = doc->resources; res; res = res->next)
    {
        /* Determine the number of messages in this resource from the
            relocation table */
        
        int msgoffset = res->object->messagetableoffset;
        int reloffset = res->object->relocationtableoffset, newreloffset;
        char *body = (char *) &res->object->hdr.class +
                               res->object->hdr.bodyoffset;
        char *msgs;
        RelocationTablePtr relocs;
        int nummsgs = 0, msgno, i;
        ResourceFileObjectTemplateHeaderPtr copy;
        Bool altered = FALSE;

        if (reloffset <= 0)
            continue;           /* This object has none */

        /* if there's no messages table, set its offset to where it would be
            if it existed */
        if (msgoffset < 0)
            msgoffset = reloffset;

        /* Walk down the relocation table looking for Messages */
        relocs = (RelocationTablePtr) ((char *)res->object + reloffset);

        for (i = 0; i < relocs->numrelocations; i++)
            if (relocs->relocations[i].directive == RELOCATE_MSGREFERENCE)
                nummsgs++;

        if (nummsgs == 0)
            continue;                                 /* None to do */

        /* Allocate an array to hold pointers to messages */

        msgptr = (char **) calloc (nummsgs, sizeof(char *));
        if (msgptr == NULL)
        {
            err = error_lookup("NoMem");
            goto fail;
        }

        /* Fill the array in with pointers into the current message block,
         *  leaving a NULL where the message is nonexistent
         */

        msgno = 0;
        msgs = (char *)res->object + msgoffset;
            /* note that msgs is not used if there are none */
        for (i = 0; i < relocs->numrelocations; i++)
            if (relocs->relocations[i].directive == RELOCATE_MSGREFERENCE)
            {
                int *word =
                    (int *) (body + relocs->relocations[i].wordtorelocate);
                if (*word >= 0)
                    msgptr[msgno] = msgs + *word;
                msgno++;
            }

        /* Now run through the incoming messages and see if any of them
         * are intended for this resource.
         */

        block
        {
            char tag[MAX_OBJECT_NAME + 2];
            int taglen;

            sprintf (tag, "%s|", res->object->hdr.name);
            taglen = strlen (tag);

            for (bufp = buf, i = 0; i < numloaded; bufp += strlen(bufp) + 1,
                 i++)
            {
                if (*bufp == '#')
                    continue;
                if (strncmp (bufp, tag, taglen) == 0)
                {
                    char *start;
                    sscanf (bufp + taglen, "%d:", &msgno);
                    start = strchr (bufp + taglen, ':');
                    if (msgno < 0 || msgno >= nummsgs)
                        numbad++;                     /* Bad number */
                    else if (start == NULL)
                        nummal++;                     /* Malformed */
                    else
                    {
                        char *prev = msgptr[msgno];

                        msgptr[msgno] = start + 1;    /* Good */            

                        if (prev == NULL || strcmp (prev, start + 1) != 0)
                            altered = TRUE;
                    }
                }
            }
        }

        /* no need to recreate this resource if no revised messages */
        if (!altered)
        {
            free ((char *) msgptr);
            msgptr = NULL;

            continue;
        }
        
        /* Now the msgptr[] array contains pointers to new message values,
         * or NULL for messages that are absent.  Calculate the total
         * size of the new object, and create the copy.
         */

        size = msgoffset;
        for (i = 0; i < nummsgs; i++)
            if (msgptr[i]) size += strlen(msgptr[i]) + 1;
        size = (size + 3) & ~3;                       /* round up to word */
        newreloffset = size;
        size += sizeof(int) + relocs->numrelocations * sizeof(RelocationRec);

        copy = (ResourceFileObjectTemplateHeaderPtr) malloc (size);
        if (copy == NULL)
        {
            err = error_lookup("NoMem");
            goto fail;
        }

        /* Copy as far as the beginning of the message table */
        memcpy ((void *) copy, (void *) res->object, msgoffset);
        copy->relocationtableoffset = newreloffset;
        copy->messagetableoffset = msgoffset;  /* in case it was NULL before */
        copy->hdr.totalsize = newreloffset -
                          offsetof(ResourceFileObjectTemplateHeaderRec, hdr);
        
        /* Now copy the relocations and add the new messages, skipping any
            that are NULL */

        block
        {
            RelocationTablePtr newrelocs =
                        (RelocationTablePtr) ((char *)copy + newreloffset);
            int msgno = 0, where = msgoffset;
            body = (char *) &copy->hdr.class + copy->hdr.bodyoffset;
            newrelocs->numrelocations = relocs->numrelocations;

            for (i = 0; i < relocs->numrelocations; i++)
            {
                newrelocs->relocations[i] = relocs->relocations[i];
                if (relocs->relocations[i].directive == RELOCATE_MSGREFERENCE)
                {
                    if (msgptr[msgno])
                    {
                        int *bodyword = (int *)
                           (body + newrelocs->relocations[i].wordtorelocate);
                        *bodyword = where - msgoffset;
                        strcpy ((char *) copy + where, msgptr[msgno]);
                        where += strlen (msgptr[msgno]) + 1;
                    }
                    msgno++;
                }
            }
        }

        /*
         * Now initiate the protocol interchange which will pass the modified
         *  object to its CSE in order that any length fields corresponding
         *  to  possibly revised messages can be updated: if a new message is
         *  longer than its original, the corresponding length field may need
         *  to be increased.
         */

        res->importing = TRUE;
        res->original = res->object;  /* freed when protocol completes */
        res->object = copy;

        EG ( fail, class_launch_cse (res->class) );
        EG ( fail, protocol_send_resed_object_load (doc, res, TRUE) );
        res->modified = FALSE;
    
        free ((char *) msgptr);
        msgptr = NULL;
    }


  fail:
    if (buf) free (buf);
    if (msgptr) free (msgptr);
    if (res)
    {
        res->importing = FALSE;
        free (res->object);
        res->object = res->original;
        res->original = NULL;
    }

    /* These are only warnings so issue error box now rather than returning
        the error */
    if (numbad)
       error_box (error_lookup (numbad == 1 ? "NumBad" : "NumBadS", numbad));
    if (nummal)
       error_box (error_lookup (nummal == 1 ? "NumMal" : "NumMalS", nummal));

    return err;
}


/*
 * Interactor for the import warning window.
 */

static error * importwarn_interactor (
    unsigned int event,
    int *buf,
    void *closure,
    Bool *consumed)
{
    ImportClosurePtr clos = (ImportClosurePtr) closure;
    MouseClickPtr mouse = (MouseClickPtr) buf;
    WindowPtr win = (WindowPtr) buf;         /* only half there */
    KeyPressPtr key = (KeyPressPtr) buf;
    DocumentPtr doc = (DocumentPtr) clos->doc;

    if (buf == NULL)                     /* we are being asked to cancel */
    {
        if (clos->goahead)
            import_messages (clos);
        else
            free (clos->buf);
        return NULL;
    }

    switch (event)
    {
    case EV_OPEN_WINDOW_REQUEST:
        if (win->handle == warnwin->handle)
        {
            *consumed = TRUE;
            warnwin->visarea = win->visarea;
            warnwin->behind = win->behind;
            ER ( swi (Wimp_OpenWindow, R1, warnwin, END) );
        }
        break;

    case EV_KEY_PRESSED:
        if (key->caret.windowhandle == warnwin->handle)
        {
            *consumed = TRUE;
            switch (key->code)
            {
            case 0x0d:   /* RETURN */
                clos->goahead = TRUE;
            case 0x1b:   /* ESCAPE */
                /* remove transient dbox */
                swi (Wimp_CreateMenu, R1, -1, END);

                /* return input focus to parent window */
                dbox_set_caret_to (&doc->window, -1);
                interactor_cancel();
                break;

            default:
                *consumed = FALSE;
                break;
            }
        }
        break;

    case EV_MOUSE_CLICK:
        if (mouse->windowhandle == warnwin->handle)
        {
            *consumed = TRUE;
            if (mouse->buttons == MB_CLICK(MB_SELECT) ||
                mouse->buttons == MB_CLICK(MB_ADJUST))
            {
                switch (mouse->iconhandle)
                {
                case I_IMPWARN_OK:
                    clos->goahead = TRUE;
                case I_IMPWARN_CANCEL:
                    swi (Wimp_CreateMenu, R1, -1, END);
                    dbox_set_caret_to (&doc->window, -1);
                    interactor_cancel ();
                    break;
                }
            }
        }
        break;

    case EV_USER_MESSAGE:
    case EV_USER_MESSAGE_RECORDED:
        {
            MessagePtr mess = (MessagePtr) buf;

            if (mess->code == MESSAGE_MENUS_DELETED)     /* MENUS_DELETED */
            {
                interactor_cancel();
                *consumed = TRUE;
            }
        }
        break;
    }

    return NULL;
}


/*
 * Import the given textfile as messages for the document specified.
 * Have to ensure that out-of-order messages are handled
 * correctly, and that where messages are absent from the file, the
 * current contents (perhaps -1) are left.
 */


error * genmsgs_import_file (DocumentPtr doc, char *filename)
{
    FILE *file = fopen(filename, "r");
    char *buf = NULL, *bufp;
    int size, numloaded = 0, c;
    error *err = NULL;

/*    dprintf ("Entered import-file of *%s*\n", filename); */

    /*
     * Read the whole messages file into memory to simplify
     * manipulating it.
     */

    if (!file) return error_lookup("CantRead", filename);
    fseek(file, 0, SEEK_END);
    size = (int) ftell(file);

    fseek(file, 0, SEEK_SET);
    
    buf = bufp = calloc(size + 1, sizeof(char));
    if (!buf)
    {
        err = error_lookup("NoMem");
        goto fail;
    }
    
    while ((c = getc(file)) != EOF)
    {
        if (c == '\n')
        {
            c = 0;
            numloaded++;
        }
        *bufp++ = c;
    }
    if (bufp != buf && *(bufp - 1) != 0)
    {
        *bufp = 0;     /* In case the last line didn't have a newline */
        numloaded++;
    }

    fclose (file);
    file = NULL;

    /* see if the file looks like a messages file */
    {
        char hdr[256];

        sprintf (hdr, message_lookup (&msgs, "MsgHdr"), RESF_VERSION);

        if (strcmp (buf, hdr) != 0)
        {
            err = error_lookup ("BadMsgHdr");
            goto fail;
        }
    } 

    /* raise Import Warning dbox to give user an opportunity to back out */
    EG (fail, swi (Wimp_CreateMenu, R1, warnwin->handle,
                               R2, warnwin->visarea.minx,
                               R3, warnwin->visarea.maxy, END) );

    closure.doc = doc;
    closure.buf = buf;
    closure.numloaded = numloaded;
    closure.goahead = FALSE;
    interactor_install (importwarn_interactor, (void *) &closure);

    /* give input focus to the dbox */
    EG (fail, dbox_set_caret_to (warnwin, -1) );
    return NULL;

  fail:
    if (buf) free (buf);
    if (file) fclose (file);

    return err;
}


/*
 * Called from the protocol routines when the import protocol completes.
 */

error * genmsgs_import_protocol_complete (ResourcePtr res, Bool ok)
{
    res->importing = FALSE;

    if (ok)
    {
        free (res->original);
        document_modified (res->owner, TRUE);
    }
    else
    {
        free (res->object);
        res->object = res->original;
    }

    res->original = NULL;

    return (ok) ? NULL : error_lookup ("ImpBadLoad", res->object->hdr.name);
}
